package org.openscheduler.modules.history.support;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Date;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.openscheduler.common.exception.SchedulerRuntimeException;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerListener;
import org.quartz.Trigger;
import org.quartz.Trigger.CompletedExecutionInstruction;
import org.quartz.TriggerKey;
import org.quartz.TriggerListener;
import org.quartz.spi.SchedulerPlugin;
import org.quartz.utils.DBConnectionManager;

/**
 * JdbcSchedulerHistoryPlugin
 * 플러그인 단독으로 사용가능하게 Java SDK 와 Quartz API 만 사용할 것.
 * 
 * 데이터 소스를 설정 후
 * org.quartz.dataSource.sampleDS.driver = org.hsqldb.jdbcDriver
 * org.quartz.dataSource.sampleDS.URL = jdbc:hsqldb:hsql://localhost/
 * org.quartz.dataSource.sampleDS.user = sa
 * org.quartz.dataSource.sampleDS.password =
 * org.quartz.dataSource.sampleDS.maxConnections = 5
 * 
 * 플러그인 설정 
 * org.quartz.plugin.JobHistoryPlugin.class = org.openscheduler.modules.history.support.JdbcSchedulerHistoryPlugin
 * org.quartz.plugin.JobHistoryPlugin.dataSource = sampleDS 
 * org.quartz.plugin.JobHistoryPlugin.tableName = QRTZ_SCHEDULER_HISTORY 
 * org.quartz.plugin.JobHistoryPlugin.excludeTriggerKeyNames=sampleTrigger1,sampleTrigger2
 * 
 * @author allbegray
 *
 */
public class JdbcSchedulerHistoryPlugin implements SchedulerPlugin, TriggerListener, SchedulerListener {

	public static final String SCHEDULER_HISTORY_CONTEXT_KEY = "JdbcSchedulerHistoryPlugin";
	
	private String tableName;
		public String getTableName() {
			return tableName;
		}
		public void setTableName(String tableName) {
			this.tableName = tableName;
		}

	private String dataSource;
		public String getDataSource() {
			return dataSource;
		}
		public void setDataSource(String dataSource) {
			this.dataSource = dataSource;
		}
		
	private String excludeTriggerKeyNames;
		public String getExcludeTriggerKeyNames() {
			return excludeTriggerKeyNames;
		}
		public void setExcludeTriggerKeyNames(String excludeTriggerKeyNames) {
			this.excludeTriggerKeyNames = excludeTriggerKeyNames;
		}

	private String insertSql;
	
	private String schedulerName;
	private String hostName;
	private String hostAddress;
	private String name;
	@SuppressWarnings("unused")
	private Scheduler scheduler;
	
	/* Query */
	
	private boolean insertHistory(SchedulerHistory sh) {
		
		if (getExcludeTriggerKeyNames() != null && getExcludeTriggerKeyNames().length() > 0) {
			for (String s : getExcludeTriggerKeyNames().split(",")) {
				if (s != null && s.length() > 0 && s.trim().equals(sh.getTriggerKeyName())) {
					return false;
				}
			}
		}
		
		Connection conn = null;
		PreparedStatement pstmt = null;
		try {
			conn = DBConnectionManager.getInstance().getConnection(getDataSource());
			pstmt = conn.prepareStatement(insertSql);
			
			int parameterIndex = 0;
			pstmt.setString(++parameterIndex, sh.getSchedulerName());
			pstmt.setString(++parameterIndex, sh.getHostName());
			pstmt.setString(++parameterIndex, sh.getHostAddress());
			
			pstmt.setString(++parameterIndex, sh.getListenerName());
			pstmt.setString(++parameterIndex, sh.getEventName());
			pstmt.setTimestamp(++parameterIndex, sh.getEventTime());
			
			pstmt.setString(++parameterIndex, sh.getJobKeyName());
			pstmt.setString(++parameterIndex, sh.getJobKeyGroupName());
			pstmt.setString(++parameterIndex, sh.getTriggerKeyName());
			pstmt.setString(++parameterIndex, sh.getTriggerKeyGroupName());
			pstmt.setString(++parameterIndex, sh.getTriggerDescription());
			pstmt.setString(++parameterIndex, sh.getTriggerfireInstanceId());
			pstmt.setTimestamp(++parameterIndex, sh.getTriggerFireTime());
			pstmt.setString(++parameterIndex, sh.getTriggerInstructionCode());
			
			pstmt.setString(++parameterIndex, sh.getErrorMsg());
			pstmt.setString(++parameterIndex, sh.getErrorCauseClassName());
			return pstmt.execute();
			
		} catch (SQLException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		} finally {
			if (pstmt != null) try { pstmt.close(); } catch (Exception e) {}
			if (conn != null) try { conn.close(); } catch (Exception e) {}
		}
	}
	
	@Override
	public void initialize(String name, Scheduler scheduler) throws SchedulerException {
		StringBuffer insertSql = new StringBuffer();
		insertSql.append("INSERT INTO ").append(getTableName()); 
		insertSql.append("(SCH_NAME,HOST_NAME,HOST_IP,LISTENER_NAME,EVENT_NAME,EVENT_TIME,JK_NAME,JK_GROUP_NAME,TK_NAME,TK_GROUP_NAME,T_DESCRIPTION,T_FIRE_INSTANCE_ID,T_FIRE_TIME,T_INSTRUCTION_CODE,ERR_MSG,ERR_CAUSE_CLASS_NAME) VALUES "); 
		insertSql.append("(?       ,?        ,?      ,?            ,?         ,?         ,?      ,?            ,?            ,?      ,?            ,?                 ,?          ,?                 ,?      ,?                   ) ");
		this.insertSql = insertSql.toString();
		
		this.name = name;
		this.scheduler = scheduler;
		try {
			InetAddress local = InetAddress.getLocalHost();
			this.hostName = local.getHostName();
			this.hostAddress = local.getHostAddress();
		} catch (UnknownHostException e) {
			throw new SchedulerRuntimeException(e);
		}
		
		this.schedulerName = scheduler.getSchedulerName();
		scheduler.getContext().put(SCHEDULER_HISTORY_CONTEXT_KEY, this);
		
		scheduler.getListenerManager().addTriggerListener(this);
		scheduler.getListenerManager().addSchedulerListener(this);
	}

	@Override
	public void start() {
		System.out.println(name + " has started.");
	}

	@Override
	public void shutdown() {
		System.out.println(name + " has shutdown.");
	}
	
	/* TriggerListener override methods start */

	@Override
	public String getName() {
		return name;
	}

	@Override
	public void triggerFired(Trigger trigger, JobExecutionContext context) {
		SchedulerHistory sh = buildSchedulerHistory(schedulerName, "triggerFired", "TriggerListener");
		sh.fillByTrigger(trigger);
		
		sh.setTriggerFireTime(context.getFireTime());
		sh.setTriggerfireInstanceId(context.getFireInstanceId());
		
		insertHistory(sh);
	}

	@Override
	public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
//			triggerFired 다음에 불려진다. 딱히 기록할 필요는 없는 듯
		
//			SchedulerHistory sh = buildSchedulerHistory("vetoJobExecution", "TriggerListener");
//			
//			sh.setJobKeyName(trigger.getJobKey().getName());
//			sh.setJobKeyGroupName(trigger.getJobKey().getGroup());
//			sh.setTrigerKeyName(trigger.getKey().getName());
//			sh.setTrigerKeyGroupName(trigger.getKey().getGroup());
		
		return false;
	}

	@Override
	public void triggerMisfired(Trigger trigger) {
		SchedulerHistory sh = buildSchedulerHistory(schedulerName, "triggerMisfired", "TriggerListener");
		sh.fillByTrigger(trigger);
		
		insertHistory(sh);
	}

	@Override
	public void triggerComplete(Trigger trigger, JobExecutionContext context, CompletedExecutionInstruction triggerInstructionCode) {
		SchedulerHistory sh = buildSchedulerHistory(schedulerName, "triggerComplete", "TriggerListener");
		sh.fillByTrigger(trigger);
		
		sh.setTriggerFireTime(context.getFireTime());
		sh.setTriggerfireInstanceId(context.getFireInstanceId());
		sh.setTriggerInstructionCode(triggerInstructionCode.toString());
		
		insertHistory(sh);
	}
	
	/* TriggerListener override methods end */
	
	/* SchedulerListener override methods start */

	@Override
	public void jobScheduled(Trigger trigger) {
		SchedulerHistory sh = buildSchedulerHistory(schedulerName, "jobScheduled", "SchedulerListener");
		sh.fillByTrigger(trigger);
		
		insertHistory(sh);
	}

	@Override
	public void jobUnscheduled(TriggerKey triggerKey) {
		SchedulerHistory sh = buildSchedulerHistory(schedulerName, "jobUnscheduled", "SchedulerListener");
		
		sh.setTriggerKeyName(triggerKey.getName());
		sh.setTriggerKeyGroupName(triggerKey.getGroup());
		
		insertHistory(sh);
	}

	@Override
	public void triggerFinalized(Trigger trigger) {
		SchedulerHistory sh = buildSchedulerHistory(schedulerName, "triggerFinalized", "SchedulerListener");
		sh.fillByTrigger(trigger);
		
		insertHistory(sh);
	}

	@Override
	public void triggerPaused(TriggerKey triggerKey) {
		SchedulerHistory sh = buildSchedulerHistory(schedulerName, "triggerPausedByTriggerKey", "SchedulerListener");
		
		sh.setTriggerKeyName(triggerKey.getName());
		sh.setTriggerKeyGroupName(triggerKey.getGroup());
		
		insertHistory(sh);
	}

	@Override
	public void triggersPaused(String triggerGroup) {
		SchedulerHistory sh = buildSchedulerHistory(schedulerName, "triggersPausedByTriggerGroup", "SchedulerListener");
		
		sh.setTriggerKeyGroupName(triggerGroup);
	
		insertHistory(sh);
	}

	@Override
	public void triggerResumed(TriggerKey triggerKey) {
		SchedulerHistory sh = buildSchedulerHistory(schedulerName, "triggerResumedByTriggerKey", "SchedulerListener");
		
		sh.setTriggerKeyName(triggerKey.getName());
		sh.setTriggerKeyGroupName(triggerKey.getGroup());
		
		insertHistory(sh);
	}

	@Override
	public void triggersResumed(String triggerGroup) {
		SchedulerHistory sh = buildSchedulerHistory(schedulerName, "triggerResumedByTriggerGroup", "SchedulerListener");
		
		sh.setTriggerKeyGroupName(triggerGroup);
		
		insertHistory(sh);
	}
	
	/* Trigger 와 Schedule 의 이벤트만으로도 데이터는 충분하다 */
	
	@Override
	public void jobAdded(JobDetail jobDetail) {
	}

	@Override
	public void jobDeleted(JobKey jobKey) {
	}

	@Override
	public void jobPaused(JobKey jobKey) {
	}

	@Override
	public void jobsPaused(String jobGroup) {
	}

	@Override
	public void jobResumed(JobKey jobKey) {
	}

	@Override
	public void jobsResumed(String jobGroup) {
	}

	@Override
	public void schedulerError(String msg, SchedulerException cause) {
		SchedulerHistory sh = buildSchedulerHistory(schedulerName, "schedulerError", "SchedulerListener");
		
		sh.setErrorMsg(msg);
		sh.setErrorCauseClassName(cause.getClass().getName());
		
		insertHistory(sh);
	}

	@Override
	public void schedulerInStandbyMode() {
		SchedulerHistory sh = buildSchedulerHistory(schedulerName, "schedulerInStandbyMode", "SchedulerListener");
		
		insertHistory(sh);
	}

	@Override
	public void schedulerStarted() {
		SchedulerHistory sh = buildSchedulerHistory(schedulerName, "schedulerStarted", "SchedulerListener");
		
		insertHistory(sh);
	}

	@Override
	public void schedulerShutdown() {
		SchedulerHistory sh = buildSchedulerHistory(schedulerName, "schedulerShutdown", "SchedulerListener");
		
		insertHistory(sh);
	}

	@Override
	public void schedulerShuttingdown() {
		SchedulerHistory sh = buildSchedulerHistory(schedulerName, "schedulerShuttingdown", "SchedulerListener");
		
		insertHistory(sh);
	}

	@Override
	public void schedulingDataCleared() {
		SchedulerHistory sh = buildSchedulerHistory(schedulerName, "schedulingDataCleared", "SchedulerListener");
		
		insertHistory(sh);
	}
	
	/* SchedulerListener override methods end */
	
	private SchedulerHistory buildSchedulerHistory(String schedulerName, String eventName, String listenerName) {
		SchedulerHistory sh = new SchedulerHistory();

		sh.setSchedulerName(schedulerName);

		sh.setEventTime(new Date());
		sh.setEventName(eventName);

		sh.setListenerName(listenerName);

		sh.setHostName(hostName);
		sh.setHostAddress(hostAddress);

		return sh;
	}
	
	/* Model */
	
	public class SchedulerHistory {

		public SchedulerHistory() {
		}

		private String schedulerName;
		private String hostName;
		private String hostAddress;

		private String listenerName;
		private String eventName;
		private Timestamp eventTime;

		private String jobKeyName;
		private String jobKeyGroupName;

		private String triggerKeyName;
		private String triggerKeyGroupName;
		private String triggerDescription;
		private String triggerfireInstanceId;
		private Timestamp triggerFireTime;
		private String triggerInstructionCode;

		private String errorMsg;
		private String errorCauseClassName;

		public String getSchedulerName() {
			return schedulerName;
		}

		public void setSchedulerName(String schedulerName) {
			this.schedulerName = schedulerName;
		}

		public String getHostName() {
			return hostName;
		}

		public void setHostName(String hostName) {
			this.hostName = hostName;
		}

		public String getHostAddress() {
			return hostAddress;
		}

		public void setHostAddress(String hostAddress) {
			this.hostAddress = hostAddress;
		}

		public String getListenerName() {
			return listenerName;
		}

		public void setListenerName(String listenerName) {
			this.listenerName = listenerName;
		}

		public String getEventName() {
			return eventName;
		}

		public void setEventName(String eventName) {
			this.eventName = eventName;
		}

		public Timestamp getEventTime() {
			return eventTime;
		}

		public void setEventTime(Timestamp eventTime) {
			this.eventTime = eventTime;
		}

		public void setEventTime(Date eventTime) {
			this.eventTime = new Timestamp(eventTime.getTime());
		}

		public String getJobKeyName() {
			return jobKeyName;
		}

		public void setJobKeyName(String jobKeyName) {
			this.jobKeyName = jobKeyName;
		}

		public String getJobKeyGroupName() {
			return jobKeyGroupName;
		}

		public void setJobKeyGroupName(String jobKeyGroupName) {
			this.jobKeyGroupName = jobKeyGroupName;
		}

		public String getTriggerDescription() {
			return triggerDescription;
		}

		public void setTriggerDescription(String triggerDescription) {
			this.triggerDescription = triggerDescription;
		}

		public String getTriggerKeyName() {
			return triggerKeyName;
		}

		public void setTriggerKeyName(String triggerKeyName) {
			this.triggerKeyName = triggerKeyName;
		}

		public String getTriggerKeyGroupName() {
			return triggerKeyGroupName;
		}

		public void setTriggerKeyGroupName(String triggerKeyGroupName) {
			this.triggerKeyGroupName = triggerKeyGroupName;
		}

		public String getTriggerfireInstanceId() {
			return triggerfireInstanceId;
		}

		public void setTriggerfireInstanceId(String triggerfireInstanceId) {
			this.triggerfireInstanceId = triggerfireInstanceId;
		}

		public Timestamp getTriggerFireTime() {
			return triggerFireTime;
		}

		public void setTriggerFireTime(Timestamp triggerFireTime) {
			this.triggerFireTime = triggerFireTime;
		}

		public void setTriggerFireTime(Date triggerFireTime) {
			this.triggerFireTime = new Timestamp(triggerFireTime.getTime());
		}

		public String getTriggerInstructionCode() {
			return triggerInstructionCode;
		}

		public void setTriggerInstructionCode(String triggerInstructionCode) {
			this.triggerInstructionCode = triggerInstructionCode;
		}

		public String getErrorMsg() {
			return errorMsg;
		}

		public void setErrorMsg(String errorMsg) {
			this.errorMsg = errorMsg;
		}

		public String getErrorCauseClassName() {
			return errorCauseClassName;
		}

		public void setErrorCauseClassName(String errorCauseClassName) {
			this.errorCauseClassName = errorCauseClassName;
		}

		public void fillByTrigger(Trigger trigger) {
			this.jobKeyName = trigger.getJobKey().getName();
			this.jobKeyGroupName = trigger.getJobKey().getGroup();
			this.triggerKeyName = trigger.getKey().getName();
			this.triggerKeyGroupName = trigger.getKey().getGroup();
			this.triggerDescription = trigger.getDescription();
		}

		@Override
		public String toString() {
			return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
		}

	}
	
}
